package sk.stuba.fiit.pki.service.impl;

import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.SignatureException;
import java.security.UnrecoverableEntryException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CRLException;
import java.security.cert.CertificateException;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.bouncycastle.tsp.TSPException;
import org.bouncycastle.tsp.TimeStampRequest;
import org.bouncycastle.tsp.TimeStampResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Service;

import sk.stuba.fiit.pki.core.CertificateManager;
import sk.stuba.fiit.pki.entity.CRLEntity;
import sk.stuba.fiit.pki.entity.CertificateEntity;
import sk.stuba.fiit.pki.service.CertificateService;
import sk.stuba.fiit.pki.service.CrlService;
import sk.stuba.fiit.pki.service.PkiService;
import sk.stuba.fiit.pki.service.SerialService;
import sk.stuba.fiit.pki.service.TimeStampService;
import sk.stuba.fiit.pki.test.GenerateRequiredCertificates;
import sk.stuba.fiit.pki.ws.schema.beans.CrlRequest;
import sk.stuba.fiit.pki.ws.schema.beans.OcspInfoType;
import sk.stuba.fiit.pki.ws.schema.beans.OcspRequest;
import sk.stuba.fiit.pki.ws.schema.beans.OcspResponse;
import sun.security.x509.X509CRLImpl;

@Service("pkiService")
public class PkiServiceImpl implements PkiService {

	@Autowired()
	private CrlService crlService;
	@Autowired()
	private CertificateService certificateService;
	@Autowired
	private SerialService serialService;
	@Autowired
	private TimeStampService timeStampService;

	public Collection<CRLEntity> findCrl() throws DataAccessException {
		return crlService.findCrl();
	}

	public void save(CRLEntity crlEntity) {
		crlService.save(crlEntity);
	}

	public Collection<CertificateEntity> findCertificates() {
		return certificateService.findCertificates();
	}

	public void delete(CertificateEntity certificateEntity) {
		certificateService.delete(certificateEntity);
	}

	public void save(CertificateEntity certificateEntity) {
		certificateService.save(certificateEntity);
	}

	public CertificateEntity findCertificateBySerialNumber(BigInteger id,  String issuer) {
		return certificateService.findCertificateBySerialNumber(id, issuer);
	}

	public BigInteger allocateCRLSerialNumber() {
		return serialService.allocateCRLSerialNumber();
	}

	public BigInteger allocateCertSerialNumber() {
		return serialService.allocateCertSerialNumber();
	}

	public BigInteger allocateTimeStampSerialNumber() {
		return serialService.allocateTimeStampSerialNumber();
	}

	public BigInteger allocateOcspSerialNumber() {
		return serialService.allocateOcspSerialNumber();
	}

	static int count = 0;
	
	public TimeStampResponse generateTimeStamp(TimeStampRequest tsRequest)
			throws UnrecoverableKeyException, KeyStoreException,
			NoSuchAlgorithmException, CertificateException,
			UnrecoverableEntryException, IllegalArgumentException, IOException,
			TSPException, GeneralSecurityException {
		BigInteger serialNumber = serialService.allocateTimeStampSerialNumber();
		
		if(count++ >5){
			count = 0;
			try{
				generateNewTSACertificate();
			}catch(Exception e){
				e.printStackTrace();
			}
		}
		
		return timeStampService.generateTimeStamp(tsRequest, serialNumber);
	}

	public byte[] generateCRL() throws InvalidKeyException, KeyStoreException,
			NoSuchAlgorithmException, CertificateException,
			UnrecoverableEntryException, CRLException, NoSuchProviderException,
			SecurityException, SignatureException, IOException {
		List<CRLEntity> crls = crlService.findCrl();
		BigInteger crlSerialNum = serialService.allocateCRLSerialNumber();
		List<BigInteger> revocateCert = certificateService
				.findRevocateCertificates();
		if (crls.size() == 0) {
			return crlService.generateFirstCRL(crlSerialNum, revocateCert);
		}

		X509CRL crlfromByte = new X509CRLImpl(crls.get(crls.size() - 1)
				.getCrl());
		return crlService.generateCRL(crlSerialNum, revocateCert, crlfromByte);
	}

	public byte[] getActualCRL(CrlRequest request) throws InvalidKeyException,
			CRLException, NoSuchProviderException, SecurityException,
			SignatureException, IOException, UnrecoverableKeyException,
			KeyStoreException, NoSuchAlgorithmException, CertificateException,
			UnrecoverableEntryException {
		List<CRLEntity> crls = crlService.findCrl();
		BigInteger crlSerialNum = serialService.allocateCRLSerialNumber();
		List<BigInteger> revocateCert = certificateService
				.findRevocateCertificates();
		if (crls.size() > 0) {
			return crlService.getActualCRL(request,crlSerialNum,revocateCert);
		} else {
			return crlService.generateFirstCRL(crlSerialNum, revocateCert);
		}
	}

	public CrlService getCrlService() {
		return crlService;
	}

	public CertificateService getCertificateService() {
		return certificateService;
	}

	public SerialService getSerialService() {
		return serialService;
	}

	public TimeStampService getTimeStampService() {
		return timeStampService;
	}

	public OcspResponse getOCSPInfo(OcspRequest req) {

		return null;
	}

	public byte[] getCertificaateBySerialNumber(BigInteger serialNum, String issuer) {
		CertificateEntity certificate = certificateService
				.findCertificateBySerialNumber(serialNum,issuer);
		if (certificate != null)
			return certificate.getCert();
		return new byte[1];
	}
	
	public byte[] getCertificateBySubjectIssuer(String issuer) {
		CertificateEntity certificate = certificateService.findCertificateBySubjectIssuer(issuer);
		if (certificate != null)
			return certificate.getCert();
		return new byte[1];
	}

	public OcspResponse getOCSPInfo(String issuer, String serialNum) {
		List<OcspInfoType> respose = new ArrayList<OcspInfoType>();
		OcspInfoType info = null;
			try {
				info = new OcspInfoType();
				CertificateEntity cert = certificateService.findCertificateBySerialNumber(new BigInteger(serialNum),issuer);
				info.setOcspreqid(serialNum);
				if (cert.getRevocate()) {
					info.setStatus("Revoked");
					info.setReason(cert.getRevocateInfo());
				} else {
					info.setStatus("Valid");
				}
				respose.add(info);
			} catch (Exception e) {
				info.setStatus("Unknown");
			}

		OcspResponse resp = new OcspResponse();
		resp.withOcspInfo(respose);
		return resp;
	}

	public void cleareAllDatabaze() {
		serialService.dealocateSerialNumber();		
		crlService.deleteAllCertificates();
		certificateService.deleteAllCertificates();
	}
	
	
	private  void generateNewTSACertificate()  throws Exception{
		CertificateManager cm = CertificateManager.getInstance();
		KeyPair kp_ts = cm.generateKeyPair("RSA", 1024);
		
		X509Certificate rootCA = cm.loadCertificate(PkiService.PATH_FILE_NAME+PkiService.ROOT_CERT_PATH, "password", PkiService.STORAGE_TYPE);
		X509Certificate certificate = cm.loadCertificate(PkiService.PATH_FILE_NAME+PkiService.CA_ROOT_PATH, "password", PkiService.STORAGE_TYPE);
		PrivateKey privateKey = cm.loadPrivateKey(PkiService.STORAGE_TYPE,PkiService.PATH_FILE_NAME+PkiService.CA_ROOT_PATH, "password");
		
		X509Certificate certTSA = cm.makeCertificate(kp_ts.getPublic(), privateKey, certificate, false,  serialService.allocateCertSerialNumber());
		X509Certificate[] rootChain = new X509Certificate[3];
		rootChain[0] = certTSA;
		rootChain[1] = certificate;
		rootChain[2] = rootCA;
		File file = new File(PkiService.PATH_FILE_NAME+PkiService.TSA_PATH);
		cm.storeCertificateWithPrivateKey(file, kp_ts.getPrivate(), rootChain, "password");
		CertificateEntity certificateEntity = new CertificateEntity();
		certificateEntity.convertCertificateToEntity(certTSA);
		certificateEntity.setPk_link(file.getAbsolutePath());
		certificateService.save(certificateEntity);
	}



}
